package com.rongji.dfish.framework.plugin.file.service;

import java.io.File;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.google.gson.reflect.TypeToken;
import com.rongji.dfish.base.Utils;
import com.rongji.dfish.base.crypt.CryptFactory;
import com.rongji.dfish.base.crypt.StringCryptor;
import com.rongji.dfish.base.util.FileUtil;
import com.rongji.dfish.framework.FrameworkHelper;
import com.rongji.dfish.framework.IdGenerator;
import com.rongji.dfish.framework.plugin.entity.PubFileRecord;
import com.rongji.dfish.framework.service.BaseService;
import com.rongji.dfish.ui.form.UploadItem;

@Service
public class FileService extends BaseService<PubFileRecord, String>{
	
	public static final String STATUS_NORMAL = "0";
	public static final String STATUS_DELETE = "1";

	public static final String CONFIG_UPLOAD_DIR = "file.uploadDir";
	public static final String CONFIG_SIZE_LIMIT = "file.sizeLimit";
	public static final String CONFIG_TYPES_FILE = "file.types.file";
	public static final String CONFIG_TYPES_IMAGE = "file.types.image";
	
	public static final String LINK_FILE = "FILE";
	
	public static final DateFormat DF = new SimpleDateFormat("yyyy/MM/dd");
	
	public static final String SECRET_KEY = "DFISH";
	
	private static StringCryptor CRY = CryptFactory.getStringCryptor(
			CryptFactory.BLOWFISH, CryptFactory.UTF8,
			CryptFactory.URL_SAFE_BASE64, SECRET_KEY);
	/**
	 * 加密文件编号
	 * 
	 * @param id
	 *            文件编号
	 * @return 加密的文件编号
	 */
	public static String encId(String id) {
		return CRY.encrypt(id);
	}

	/**
	 * 解密编号
	 * 
	 * @param encId
	 *            加密的编号
	 * @return 编号
	 */
	public static String decId(String encId) {
		try {
			return CRY.decrypt(encId);
		} catch (Exception e) {
			FrameworkHelper.LOG.error("解密编号出错", e);
			return "";
		}
	}
	
	/**
	 * 路径分隔符
	 * @return
	 */
	public static String getDirSeparator() {
		return "/";
	}
	
	/**
	 * 文件存放目录
	 * @return
	 */
	public static String getUploadDir() {
		String uploadDir = FrameworkHelper.getSystemConfig(CONFIG_UPLOAD_DIR, "../upload/");
		String dirSeparator = getDirSeparator();
		if (Utils.notEmpty(uploadDir) && !uploadDir.endsWith(dirSeparator)) {
			uploadDir += dirSeparator;
		}
		return uploadDir;
	}
	
	/**
	 * 文件限制大小
	 * @return
	 */
	public static String getSizeLimit() {
		return FrameworkHelper.getSystemConfig(CONFIG_SIZE_LIMIT, "2M");
	}
	
	/**
	 * 文件上传支持的类型
	 * @return
	 */
	public static String getTypesFile() {
		return FrameworkHelper.getSystemConfig(CONFIG_TYPES_FILE, "*.doc;*.docx;*.xls;*.xlsx;*.ppt;*.pptx;*.zip;*.rar;*.jpg;*.gif;*.png;*.vsd;*.pot;*.pps;*.txt;*.rtf;*.pdf;*.epub;*.wps;*.et;*.dps");
	}
	
	/**
	 * 图片上传支持的类型
	 * @return
	 */
	public static String getTypesImage() {
		return FrameworkHelper.getSystemConfig(CONFIG_TYPES_IMAGE, "*.jpg;*.gif;*.png");
	}
	
	/**
	 * 获取新编号
	 * @return
	 */
	protected String getNewId() {
		return IdGenerator.getTimeSortedId();
	}
	
	/**
	 * 保存文件以及文件记录
	 * @param fileData
	 * @param loginUserId
	 * @throws Exception
	 */
	public UploadItem saveFile(MultipartFile fileData, String loginUserId) throws Exception {
		if (fileData == null) {
			return null;
		}
		
		String fileName = fileData.getOriginalFilename().replace(" ", "");
		long fileSize = fileData.getSize();
		String fileId = getNewId();
		
		// 直接用文件编号作为文件名
		String saveFileName = fileId + FileUtil.getFileExtName(fileName);
		
//		String fileType = null;
		Date now = new Date();
		
		String dirSeparator = getDirSeparator();
		String uploadDir = getUploadDir();
		// 补路径
		if (uploadDir.lastIndexOf(dirSeparator) < uploadDir.length()-1) {
			uploadDir += dirSeparator;
		}
		String fileDir = DF.format(now);
		
		PubFileRecord fileRecord = new PubFileRecord();
		fileRecord.setFileId(fileId);
		fileRecord.setFileName(fileName);
		fileRecord.setFileSize(fileSize);
		fileRecord.setFileCreator(loginUserId);
		fileRecord.setCreateTime(now);
		fileRecord.setUpdateTime(now);
		fileRecord.setFileLink(LINK_FILE);
		fileRecord.setFileKey(fileId);
		fileRecord.setFileUrl(fileDir + dirSeparator + saveFileName);
		fileRecord.setFileStatus(STATUS_NORMAL);
		save(fileRecord);
		
		// 这边没异常,不管有没保存成功都会增加文件记录
		FileUtil.saveFile(fileData.getInputStream(), uploadDir + fileDir, saveFileName);
		return parseUploadItem(fileRecord);
	}
	
	/**
	 * 更新文件记录状态
	 * @param fileId
	 * @param fileStatus
	 */
	public void updateFileStatus(String fileId, String fileStatus) {
		if (Utils.isEmpty(fileId) || Utils.isEmpty(fileStatus)) {
			return;
		}
		getPubCommonDAO().bulkUpdate("UPDATE PubFileRecord t SET t.fileStatus=? WHERE t.fileId=?", fileStatus, fileId);
	}
	
	/**
	 * 更新文件记录状态
	 * @param fileLink
	 * @param fileKey
	 * @param fileStatus
	 */
	public void updateFileStatus(String fileLink, String fileKey, String fileStatus) {
		if (Utils.isEmpty(fileLink) || Utils.isEmpty(fileKey) || Utils.isEmpty(fileStatus)) {
			return;
		}
		getPubCommonDAO().bulkUpdate("UPDATE PubFileRecord t SET t.fileStatus=? WHERE t.fileLink=? AND t.fileKey=?", fileStatus, fileLink, fileKey);
	}
	
	/**
	 * 根据文件记录获取文件
	 * @param fileRecord
	 * @return
	 */
	public File getFile(PubFileRecord fileRecord) {
		if (fileRecord == null) {
			return null;
		}
		File file = new File(getUploadDir() + fileRecord.getFileUrl());
		if (!file.exists()) {
			return null;
		}
		return file;
	}
	
	/**
	 * 根据文件记录获取文件
	 * @param fileRecords
	 * @return
	 */
	public List<File> getFiles(List<PubFileRecord> fileRecords) {
		if (Utils.isEmpty(fileRecords)) {
			return Collections.emptyList();
		}
		List<File> fileList = new ArrayList<File>();
		for (PubFileRecord fileRecord : fileRecords) {
			File file = getFile(fileRecord);
			if (file != null) {
				fileList.add(file);
			}
		}
		return fileList;
	}
	
	/**
	 * 根据文件记录获取文件
	 * @param itemJson
	 * @return
	 */
	public List<File> getFiles(String itemJson) {
		return getFiles(itemJson, false);
	}
	
	/**
	 * 根据文件记录获取文件
	 * @param itemJson
	 * @param single
	 * @return
	 */
	private List<File> getFiles(String itemJson, boolean single) {
		if (Utils.isEmpty(itemJson)) {
			return Collections.emptyList();
		}
		List<UploadItem> itemList = parseUploadItems(itemJson);
		List<String> fileIds = new ArrayList<String>();
		for (UploadItem item : itemList) {
			String fileId = decId(item.getId());
			if (Utils.isEmpty(fileId)) {
				continue;
			}
			fileIds.add(fileId);
			if (single) {
				break;
			}
		}
		List<PubFileRecord> fileRecords = findFileRecords(fileIds.toArray(new String[]{}));
		return getFiles(fileRecords);
	}
	
	/**
	 * 根据文件记录获取文件,方便项目中导入文件时候使用
	 * @param itemJson
	 * @return
	 */
	public File getFile(String itemJson) {
		List<File> fileList = getFiles(itemJson, true);
		if (Utils.notEmpty(fileList)) {
			return fileList.get(0);
		}
		return null;
	}
	
	/**
	 * 查询可用的文件记录
	 * @param fileLink
	 * @param fileKey
	 * @return
	 */
	public List<PubFileRecord> findFileRecords(String fileLink, String fileKey) {
		if (Utils.isEmpty(fileLink) || Utils.isEmpty(fileKey)) {
			return Collections.emptyList();
		}
		@SuppressWarnings("unchecked")
        List<PubFileRecord> result = (List<PubFileRecord>) getPubCommonDAO().getQueryList("FROM PubFileRecord t WHERE t.fileLink=? AND t.fileKey=? AND t.fileStatus=?", fileLink, fileKey, STATUS_NORMAL);
		return result;
	}
	
	/**
	 * 根据链接查询文件数据项
	 * @param fileLink
	 * @param fileKey
	 * @return
	 */
	public List<UploadItem> findUploadItems(String fileLink, String fileKey) {
		List<PubFileRecord> fileRecords = findFileRecords(fileLink, fileKey);
		return parseUploadItems(fileRecords);
	}
	
	/**
	 * 查询可用的文件记录
	 * @param fileLink
	 * @param fileKey
	 * @return
	 */
	public List<PubFileRecord> findFileRecords(String... fileId) {
		if (Utils.isEmpty(fileId)) {
			return Collections.emptyList();
		}
		if (fileId.length > BATCH_SIZE) { //
			throw new UnsupportedOperationException("单批查询支持的最大数量[" + BATCH_SIZE + "]");
		}
		List<Object> params = new ArrayList<Object>();
		for (String item : fileId) {
			if (item == null) {
				continue;
			}
			params.add(item);
		}
		if (params.isEmpty()) {
			return Collections.emptyList();
		}
		@SuppressWarnings("unchecked")
        List<PubFileRecord> result = (List<PubFileRecord>) getPubCommonDAO().getQueryList("FROM PubFileRecord t WHERE t.fileId IN(" + getParamStr(params.size()) + ")", params.toArray());
		return result;
	}
	
	/**
	 * 查询可用文件数据项
	 * @param fileId
	 * @return
	 */
	public List<UploadItem> findUploadItems(String... fileId) {
		List<PubFileRecord> fileRecords = findFileRecords(fileId);
		return parseUploadItems(fileRecords);
	}
	
	/**
	 * 更新文件链接
	 * @param itemJson
	 * @param fileLink
	 * @param fileKey
	 */
	public void updateFileLink(String itemJson, String fileLink, String fileKey) {
		List<UploadItem> itemList = parseUploadItems(itemJson);
		updateFileLink(itemList, fileLink, fileKey);
	}
	
	/**
	 * 更新文件链接
	 * @param itemList
	 * @param fileLink
	 * @param fileKey
	 */
	public void updateFileLink(List<UploadItem> itemList, String fileLink, String fileKey) {
		if (Utils.isEmpty(itemList) || Utils.isEmpty(fileLink) || Utils.isEmpty(fileKey)) {
			return;
		}
		if (itemList.size() > BATCH_SIZE) {
			// FIXME 待分批处理,理论上应该不会出现这么多的附件
		}
		
		List<String> newFileIds = new ArrayList<String>();
		for (UploadItem item : itemList) {
			newFileIds.add(decId(item.getId()));
		}
		// 这里需要将旧文件标为删除,否则之前的文件无法删除
		@SuppressWarnings("unchecked")
        List<String> oldFileIds = (List<String>) getPubCommonDAO().getQueryList("SELECT t.fileId FROM PubFileRecord t WHERE t.fileLink=? AND t.fileKey=? AND t.fileStatus=?", fileLink, fileKey, STATUS_NORMAL);
		
		List<String> insertIds = new ArrayList<String>(newFileIds);
		List<String> deleteIds = new ArrayList<String>(oldFileIds);
		insertIds.removeAll(oldFileIds);
		deleteIds.removeAll(newFileIds);
		if (Utils.notEmpty(insertIds)) { // 这边的insert相当于更新附件链接和状态
			List<Object> params = new ArrayList<Object>();
			params.add(fileLink);
			params.add(fileKey);
			params.add(new Date());
			params.addAll(insertIds);
			getPubCommonDAO().bulkUpdate("UPDATE PubFileRecord t SET t.fileLink=?,t.fileKey=?,t.updateTime=? WHERE t.fileId IN(" + getParamStr(insertIds.size()) + ")", params.toArray());
		}
		if (Utils.notEmpty(deleteIds)){ // 这里的delete 相当于把附件状态标志为删除
			List<Object> params = new ArrayList<Object>();
			params.add(STATUS_DELETE);
			params.add(new Date());
			params.addAll(deleteIds);
			getPubCommonDAO().bulkUpdate("UPDATE PubFileRecord t SET t.fileStatus=?,t.updateTime=? WHERE t.fileId IN(" + getParamStr(deleteIds.size()) + ")", params.toArray());
		}
	}
	
	/**
	 * 转换成文件数据项
	 * @param itemJson
	 * @return
	 */
	public static List<UploadItem> parseUploadItems(String itemJson) {
		if (Utils.isEmpty(itemJson)) {
			return Collections.emptyList();
		}
		try {
			List<UploadItem> itemList = UploadItem.GSON.fromJson(itemJson, new TypeToken<List<UploadItem>>(){}.getType());
			return itemList;
        } catch (Exception e) {
        	return Collections.emptyList();
        }
	}
	
	/**
	 * 转换成文件数据项
	 * @param fileRecords
	 * @return
	 */
	public static List<UploadItem> parseUploadItems(List<PubFileRecord> fileRecords) {
		if (Utils.isEmpty(fileRecords)) {
			return null;
		}
		List<UploadItem> uploadItems = new ArrayList<UploadItem>();
		for (PubFileRecord fileRecord : fileRecords) {
			UploadItem item = parseUploadItem(fileRecord);
			uploadItems.add(item);
		}
		return uploadItems;
	}
	
	/**
	 * 转换成文件数据项
	 * @param fileRecord
	 * @return
	 */
	public static UploadItem parseUploadItem(PubFileRecord fileRecord) {
		UploadItem item = new UploadItem();
		String encId = encId(fileRecord.getFileId());
		item.setId(encId);
		item.setName(fileRecord.getFileName());
		item.setSize(fileRecord.getFileSize());
		return item;
	}
	
}
